package com.homelock.common.sequence;

/**
 * @author chenmfa
 * @date 2019年1月11日01:26:08
 * @description 单台机器生成雪花id的工具类
 * @description 雪花id原理
 * 0 - 00000000 00000000 00000000 00000000 00000000 0 - 00000 - 00000 - 00000000 0000
 * 第一位未使用(保留)，接下来的41位为毫秒级时间(41位的长度可以使用69年，从1970-01-01 08:00:00),
 * 然后是5位datacenterId（最大支持2^5＝32个，二进制表示从00000-11111，也即是十进制0-31），
 * 和5位workerId（最大支持2^5＝32个，原理同datacenterId），所以datacenterId*workerId最多支持部署1024个节点，
 * 最后12位是毫秒内的计数（12位的计数顺序号支持每个节点每毫秒产生2^12＝4096个ID序号）.
 * 所有位数加起来共64位，恰好是一个Long型（转换为字符串长度为18）.
 * 单台机器实例，通过时间戳保证前41位是唯一的，分布式系统多台机器实例下，
 * 通过对每个机器实例分配不同的datacenterId和workerId避免中间的10位碰撞。
 * 最后12位每毫秒从0递增生产ID，再提一次：每毫秒最多生成4096个ID，每秒可达4096000个。
 * 理论上，只要CPU计算能力足够，单机每秒可生产400多万个，网传实测300w+
 */
public class SnowflakeIdWorker {
//  开始时间, 2019年1月10日开始
  private static final long twepoch = 1557856570224L;
//  最大机器数5位, 即2^5 共32台                                       
  private static final long workerIdBits = 5L;
//  数据中心的最大节点数, 2^5 32台
  private static final long dataCenterIdBits = 5L;
//  最大的机器id(相传这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
  private static final long maxWorkerId = ~(-1L << workerIdBits);
//  最大的数据中心id
  private static final long maxDataCenterId = ~(-1L << dataCenterIdBits);
//  序列号位数
  private static final long sequenceBits = 12L;
//  机器需要向左移12位
  private static final long workerIdShift = sequenceBits;
//  数据中心向左移 17 位
  private static final long dataCenterIdShift = workerIdShift + workerIdBits;
//  时间戳左移22位
  private static final long timeStampShift = dataCenterIdShift + dataCenterIdBits;
//  生成序列的掩码(0b111111111111=0xfff=4095)
  private static final Long sequenceMask = ~(-1L << sequenceBits);
  
//  当前机器id (0-31)
  private long workerId;
//  数据中心id (0-31)
  private long dataCenterId;
//  毫秒内的序列号 (0-4095)
  private long sequence = 0L;
//  上一次生成id的时间戳
  private long lastTimeStamp = -1L;
  
  /**
   * @description 构造函数
   * @param workerId 机器id
   * @param dataCenterId 数据中心id
   */
  SnowflakeIdWorker(long workerId, long dataCenterId){
    if( workerId < 0 || workerId > maxWorkerId ){
      throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
    }
    if( dataCenterId < 0 || dataCenterId > maxDataCenterId ){
      throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDataCenterId));
    }
    this.workerId = workerId;
    this.dataCenterId = dataCenterId;
  }
  
  /**
   * @description 获取线程安全的SnowflakeId
   * @return Long
   */
  public synchronized Long nextId(){
    long now = timeGen();
//    当前时间小于设定时间时, 抛出时钟异常
    if(now < lastTimeStamp){
      throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimeStamp - now));
    }
//    如果是同一时间生成的, 则叠加获取毫秒内的序列号
    if( lastTimeStamp == now ){
      sequence = ( sequence + 1 ) & sequenceMask;
      //如果序列溢出
      if( 0 == sequence){
        //阻塞至下个时间点
        now = tilNextMilis(lastTimeStamp);
      }
    }else{
      sequence = 0;
    }
    
    lastTimeStamp = now;
    //通过唯位移算法获取到id
    return ((now - twepoch) << timeStampShift)
            | (dataCenterId << dataCenterIdShift)
            | (workerId << workerIdShift)
            | sequence;
  }
  
  private Long tilNextMilis(long lastTimeStamp){
    long current = timeGen();
    while( lastTimeStamp >= current){
      current = timeGen();
    }
    return current;
  }
  
  private Long timeGen(){
    return System.currentTimeMillis();
  }
  
  public static void main(String[] args) {
    System.out.println(new SnowflakeIdWorker(0, 0).nextId());
  }
}
